package orchestration

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport"
	"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
	"github.com/sirupsen/logrus"
	"net/url"
	"openstack_api/client"
	my_common "openstack_api/common"
)

type Image struct {
	Name string `json:"name" required:"true"`

	// flavorID is the the image ID.
	ID string `json:"id,omitempty"`

	// Visibility defines who can see/use the image.
	//Visibility *images.ImageVisibility `json:"visibility,omitempty"`

	// Hidden is whether the image is listed in default image list or not.
	Hidden bool `json:"os_hidden,omitempty"`

	// Tags is a set of image tags.
	Tags []string `json:"tags,omitempty"`

	// ContainerFormat is the format of the
	// container. Valid values are ami, ari, aki, bare, and ovf.
	ContainerFormat string `json:"container_format,omitempty"`

	// DiskFormat is the format of the disk. If set,
	// valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
	// and iso.
	DiskFormat string `json:"disk_format,omitempty"`

	// MinDisk is the amount of disk space in
	// GB that is required to boot the image.
	MinDisk int `json:"min_disk,omitempty"`

	// MinRAM is the amount of RAM in MB that
	// is required to boot the image.
	MinRAM int `json:"min_ram,omitempty"`

	// protected is whether the image is not deletable.
	Protected bool `json:"protected,omitempty"`

	// properties is a set of properties, if any, that
	// are associated with the image.
	Properties map[string]string `json:"properties,omitempty"`

	// url of webdownload
	UploadUri string `json:"upload_uri"`

	// upload methond
	UploadMethod imageimport.ImportMethod `json:"upload_method"`

	//callback url
	CallBackUrl string `json:"callback_url" gorm:"callback_url" binding:"omitempty"`
}

type ImageUpdate struct {
	Name string `json:"name,omitempty"`

	// flavorID is the the image ID.
	//ID string `json:"id,omitempty"`

	// Visibility defines who can see/use the image.
	//Visibility *images.ImageVisibility `json:"visibility,omitempty"`

	// Hidden is whether the image is listed in default image list or not.
	//Hidden *bool `json:"os_hidden,omitempty"`

	// Tags is a set of image tags.
	Tags []string `json:"tags,omitempty"`

	// ContainerFormat is the format of the
	// container. Valid values are ami, ari, aki, bare, and ovf.
	//ContainerFormat string `json:"container_format,omitempty"`

	// DiskFormat is the format of the disk. If set,
	// valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
	// and iso.
	//DiskFormat string `json:"disk_format,omitempty"`

	// MinDisk is the amount of disk space in
	// GB that is required to boot the image.
	MinDisk int `json:"min_disk,omitempty"`

	// MinRAM is the amount of RAM in MB that
	// is required to boot the image.
	MinRAM int `json:"min_ram,omitempty"`

	// properties is a set of properties, if any, that
	// are associated with the image.
	Properties map[string]string `json:"properties,omitempty"`
}

type Properties struct {
	OSVersion           string `json:"os_version,omitempty"`
	OSType              string `json:"os_type,omitempty"`
	OSDistro            string `json:"os_distro,omitempty"`
	HwMachineType       string `json:"hw_machine_type,omitempty"`
	HwQemuGuestAgent    *bool  `json:"hw_qemu_guest_agent,omitempty"`
	ImgHideHypervisorId string `json:"img_hide_hypervisor_id,omitempty"`
	OSAdminUser         string `json:"os_admin_user,omitempty"`
	DiskBus             string `json:"hw_disk_bus,omitempty"`
}

func (i Image) getClient() (serviceClient *gophercloud.ServiceClient) {
	serviceClient = client.GetImageClient()
	return
}

func (i Image) errRet(c *gin.Context, code int, message interface{}) {
	c.JSON(code, gin.H{
		"code":    code,
		"message": message,
	})
}

func (i Image) CreateImage(c *gin.Context) {
	logrus.Info("Create Image, get started")
	imageClient := i.getClient()

	_, ok := binding.Validator.Engine().(*validator.Validate)
	if ok != false {

	}
	//if ok {
	//	err := validate.RegisterValidation("resources", checkResources)
	//	if err != nil {
	//		return
	//	}
	//}
	//_ = zhs.RegisterDefaultTranslations(validate, trans)
	err := c.ShouldBindJSON(&i)
	if err != nil {
		//transletError := exceptions.Translate(validate, err)
		logrus.Error("Create Image, parameter validate error, ", err)
		i.errRet(c, 500, err.Error())
		return
	}
	//jsonParam, err := json.Marshal(i)
	//if (i.UploadMethod != imageimport.GlanceDirectMethod)(i.UploadMethod != imageimport.WebDownloadMethod) {
	//
	//}
	logrus.Info("Create Image, params = ", i)
	//_, statusCode, _, err := my_common.ImageURLTest([]byte("{}"), i.UploadUri)
	//if statusCode == 404 {
	//	i.errRet(c, 404, "upload_uri can't access")
	//	return
	//}

	imgCreateOpts := images.CreateOpts{
		Name:            i.Name,
		ContainerFormat: i.ContainerFormat,
		DiskFormat:      i.DiskFormat,
		MinDisk:         i.MinDisk,
		MinRAM:          i.MinRAM,
		Properties:      i.Properties,
	}
	//err = json.Unmarshal(jsonParam, &imgCreateOpts)
	logrus.Info("Create Image, parameter validate success, parameter= ", imgCreateOpts)
	//if imgCreateOpts.ContainerFormat == "" {
	//	imgCreateOpts.ContainerFormat = "bare"
	//}

	//p := structs.Map(i.Properties)
	//imgCreateOpts.Properties = i.Properties

	if err == nil {
		if imgCreateOpts.ContainerFormat == "" {
			imgCreateOpts.ContainerFormat = "bare"
		}
		imgCreateOpts.Properties = i.Properties
		imgCreateOpts.Properties["hw_video_model"] = "qxl"

		img, err := images.Create(imageClient, images.CreateOptsBuilder(
			&imgCreateOpts,
		)).Extract()
		if err != nil {
			logrus.Error("Create Image, and call openstack api failed, with error=, ", err)
			i.errRet(c, 401, err.Error())
			return
		}
		uploadUri := i.UploadUri
		uploadUri, err = url.QueryUnescape(i.UploadUri)

		importRet := imageimport.Create(imageClient, img.ID, imageimport.CreateOptsBuilder(
			imageimport.CreateOpts{
				Name: i.UploadMethod, URI: uploadUri,
			},
		)).ExtractErr()
		if importRet != nil {
			logrus.Error("Upload Image, and call openstack api failed, with error=", importRet)
			i.errRet(c, 500, importRet)
			return
		}

		c.JSON(200, img)
		if i.CallBackUrl != "" {
			logrus.Info("Create Image, get start call back , callback_url=", i.CallBackUrl)
			go i.CreateCallback(i.CallBackUrl, img.ID, images.ImageStatusActive)
		}
	} else {
		i.errRet(c, 500, err.Error())
	}
}

// 回调
func (i Image) CreateCallback(callBackUrl string, id string, expectStatus images.ImageStatus) {
	//times := 0
	//img, _ := i.getImage(id)
	logrus.Info("image callback start, callback_url=", callBackUrl, ",image_id=", id)
	err := i.WaitForStatus(id, expectStatus, 7200)
	if err != nil {
		logrus.Error(err.Error())
	}

	//for {
	//	time.Sleep(time.Second * 20)
	//	logrus.Info("Create Image, wait image create success, times=", times)
	//	times += 1
	//	img, _ = i.getImage(id)
	//	if img.Status != expectStatus {
	//
	//		if img.Status == images.ImageStatusImporting {
	//			continue
	//		}
	//		if (times >= 3 && img.Status == images.ImageStatusQueued) || (times >= 200) {
	//			data, _ := json.Marshal(img)
	//			img.Status = "error"
	//			//fmt.Println(data)
	//			body, statuscode, header, e := my_common.HttpPostJson(data, callBackUrl)
	//			fmt.Println(body, statuscode, header, e)
	//			if times >= 200 {
	//				logrus.Error("Create Image, wait image created and do_callback error, but request time out ")
	//			} else {
	//				logrus.Error("Create Image, wait image created and do_callback error, but image still queued , image may be can't download")
	//			}
	//			break
	//		}
	//
	//	} else {
	//		break
	//	}
	//
	//}
	img, _ := i.getImage(id)
	data, _ := json.Marshal(img)
	//fmt.Println(data)
	logrus.Info("image create callback , img=", string(data))

	body, statuscode, header, e := my_common.HttpPostJson(data, callBackUrl)
	if e != nil || statuscode != 200 {
		logrus.Error("Create Image, post callback failed , error=", e, ",body=", string(body), ",status_code=", statuscode, ",header=", header)
	} else {
		logrus.Info("Create Image, post callback success", ",body=", string(body), ",status_code=", statuscode, ",header=", header)
	}

}

func (i Image) WaitForStatus(id string, status images.ImageStatus, secs int) error {
	return gophercloud.WaitFor(secs, func() (bool, error) {
		current, err := i.getImage(id)
		if err != nil {
			return false, err
		}

		if current.Status == status {
			return true, nil
		}

		return false, nil
	})
}

func (i Image) ListImages(c *gin.Context) {
	//imageClient := i.getClient()
	allParam := c.Request.URL.Query()
	fmt.Println(allParam)
	var listOpts images.ListOpts
	paramToMap := make(map[string]string)
	for key, value := range allParam {
		paramToMap[key] = value[0]
	}
	paramJson, err := json.Marshal(paramToMap)
	if err != nil {
		i.errRet(c, 500, err.Error())
	}
	err = json.Unmarshal(paramJson, &listOpts)
	if err != nil {
		i.errRet(c, 500, err.Error())
	}
	fmt.Println(listOpts)
	allImages, err := i.listImages(listOpts)
	if err != nil {
		i.errRet(c, 500, err.Error())
		return
	}
	c.JSON(200, allImages)
	return
}

func (i Image) GetImage(c *gin.Context) {
	//imageClient := i.getClient()
	//allParam := c.Request.URL.Query()
	uuid := c.Param("uuid")
	img, err := i.getImage(uuid)
	if err != nil {
		i.errRet(c, 404, err.Error())
		return
	}
	c.JSON(200, img)
}

//func (i Image) UpdateImage(c *gin.Context) {
//	//imageClient := i.getClient()
//	//allParam := c.Request.URL.Query()
//	logrus.Info("Update Image, get started")
//	uuid := c.Param("uuid")
//	img, err := i.getImage(uuid)
//	imageClient := i.getClient()
//
//	if err != nil {
//		i.errRet(c, 404, err.Error())
//		return
//	}
//	//err = c.ShouldBindJSON(&i)
//	//if err != nil {
//	//	logrus.Error("Update image: validate parameter failed, error=", err)
//	//	i.errRet(c, 500, err.Error())
//	//	return
//	//}
//
//	images.Update(imageClient, uuid, images.UpdateOptsBuilder(images.UpdateOpts{}))
//
//	c.JSON(200, img)
//}

func (i Image) getImage(id string) (*images.Image, error) {
	imageClient := i.getClient()
	img, err := images.Get(imageClient, id).Extract()
	return img, err
}

func (i Image) listImages(opts images.ListOpts) ([]images.Image, error) {
	imageClient := i.getClient()
	allPagers, err := images.List(imageClient, images.ListOptsBuilder(opts)).AllPages()

	if err != nil {
		return nil, err
	}
	allImages, err := images.ExtractImages(allPagers)

	return allImages, err
}

func (i Image) UpdateImage(c *gin.Context) {
	//imageClient := i.getClient()
	//allParam := c.Request.URL.Query()

	uuid := c.Param("uuid")
	logrus.Info("Update image, got id=", uuid)
	_, err := i.getImage(uuid)
	if err != nil {
		logrus.Error("Update image, can't find image with id=", uuid)
		i.errRet(c, 404, err.Error())
		return
	}
	var update ImageUpdate
	err = c.ShouldBindJSON(&update)

	if err != nil {
		logrus.Error("Update image, validated parameter failed, id=", uuid, ", error=", err.Error())
		i.errRet(c, 500, err.Error())
		return
	}
	image, err := i.updateImage(uuid, update)
	if err != nil {
		logrus.Error("Update image, failed with id=", uuid, ",error=", err.Error())
		i.errRet(c, 500, err.Error())
		return
	}
	logrus.Info("Update image, success, id=", uuid)
	c.JSON(200, image)
}

func (i Image) updateImage(id string, update ImageUpdate) (image *images.Image, err error) {
	imageClient := i.getClient()
	opts := images.UpdateOpts{}
	if update.Name != "" {
		opts = append(opts, images.ReplaceImageName{NewName: update.Name})
	}
	if update.Tags != nil {
		opts = append(opts, images.ReplaceImageTags{NewTags: update.Tags})
	}
	if update.Properties != nil {
		for key, value := range update.Properties {
			opts = append(opts, images.UpdateImageProperty{Op: images.AddOp, Name: key, Value: value})
		}
	}
	if update.MinRAM != 0 {
		opts = append(opts, images.ReplaceImageMinRam{NewMinRam: update.MinRAM})
	}
	if update.MinDisk != 0 {
		opts = append(opts, images.ReplaceImageMinDisk{NewMinDisk: update.MinDisk})
	}

	image, err = images.Update(imageClient, id, images.UpdateOptsBuilder(opts)).Extract()
	return image, err
}

func (i Image) DeleteImage(c *gin.Context) {

	//imageClient := i.getClient()
	//allParam := c.Request.URL.Query()
	uuid := c.Param("uuid")
	logrus.Info("Delete image, got id=", uuid)
	_, err := i.getImage(uuid)
	if err != nil {
		logrus.Error("Delete image, can't found image with id=", uuid)
		i.errRet(c, 404, err.Error())
		return
	}
	err = i.deleteImage(uuid)
	if err != nil {
		logrus.Error("Delete image, failed, error=", err.Error())
		i.errRet(c, 500, err.Error())
	}
	c.JSON(202, nil)
}

func (i Image) deleteImage(id string) error {
	imageClient := i.getClient()
	err := images.Delete(imageClient, id).ExtractErr()
	return err
}
